En esta sección podemos ver el filtro de las las ofertas de casas, de la zona norte de la ciudad además de asegurarnos que estén ubicados en el mapa en la zona norte de Cali.
# --- 1. Filtro de la Base de Datos ---
# Filtrar para obtener solo casas en la Zona Norte
casas_norte <- vivienda_exportada %>%
filter(tipo == "Casa", zona == "Zona Norte")
# Presentar los primeros 3 registros
print("Primeros 3 registros de casas en la Zona Norte:")
## [1] "Primeros 3 registros de casas en la Zona Norte:"
head(casas_norte, 3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1209 Zona N… 02 5 320 150 2 4 6
## 2 1592 Zona N… 02 5 780 380 2 3 3
## 3 4057 Zona N… 02 6 750 445 NA 7 6
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Tablas para comprobar la consulta
print("Verificación de 'tipo' en la base filtrada:")
## [1] "Verificación de 'tipo' en la base filtrada:"
table(casas_norte$tipo)
##
## Casa
## 722
print("Verificación de 'zona' en la base filtrada:")
## [1] "Verificación de 'zona' en la base filtrada:"
table(casas_norte$zona)
##
## Zona Norte
## 722
# --- Mapeo de las propiedades ---
# Crear un mapa interactivo con leaflet
mapa_casas <- leaflet(data = casas_norte) %>%
addTiles() %>% # Añade el mapa base por defecto
addCircleMarkers(
~longitud, ~latitud,
popup = ~paste("Barrio:", barrio, "<br>", "Precio:", preciom, "M"),
radius = 5,
stroke = FALSE,
fillOpacity = 0.8
) %>%
addControl("Casas en Venta - Zona Norte", position = "topright")
# Mostrar el mapa
mapa_casas
# Ver las primeras filas para asegurarnos que cargó correctamente
head(vivienda_exportada)
## # A tibble: 6 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1147 Zona O… <NA> 3 250 70 1 3 6
## 2 1169 Zona O… <NA> 3 320 120 1 2 3
## 3 1350 Zona O… <NA> 3 350 220 2 2 4
## 4 5992 Zona S… 02 4 400 280 3 5 3
## 5 1212 Zona N… 01 5 260 90 1 2 3
## 6 1724 Zona N… 01 5 240 87 1 3 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Contar el número total de filas completamente duplicadas
numero_duplicados <- sum(duplicated(vivienda_exportada))
print(paste("Se encontraron", numero_duplicados, "filas completamente duplicadas."))
## [1] "Se encontraron 1 filas completamente duplicadas."
# Contar los NAs en la columna 'parqueaderos'
na_parqueaderos <- sum(is.na(vivienda_exportada$parqueaderos))
# Calcular el porcentaje de NAs
porcentaje_na <- (na_parqueaderos / nrow(vivienda_exportada)) * 100
print(paste("Número de valores faltantes en 'parqueaderos':", na_parqueaderos))
## [1] "Número de valores faltantes en 'parqueaderos': 1605"
print(paste("Porcentaje de valores faltantes:", round(porcentaje_na, 4), "%"))
## [1] "Porcentaje de valores faltantes: 19.2862 %"
# Imputar NAs con la media en todas las columnas numéricas
vivienda_exportada <- vivienda_exportada %>%
mutate(across(where(is.numeric),
~ifelse(is.na(.), mean(., na.rm = TRUE), .)))
# Verificar que ya no hay NAs en las columnas numéricas
# El resultado debería ser 0 para las columnas que eran numéricas.
sapply(vivienda_exportada, function(x) sum(is.na(x)))
## id zona piso estrato preciom areaconst
## 0 3 2638 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 0 0 0 3 3 0
## latitud
## 0
#identificación de datos faltantes
colSums(is.na(vivienda_exportada)) # NA por columna
## id zona piso estrato preciom areaconst
## 0 3 2638 0 0 0
## parqueaderos banios habitaciones tipo barrio longitud
## 0 0 0 3 3 0
## latitud
## 0
summary(vivienda_exportada)
## id zona piso estrato
## Min. : 1 Length:8322 Length:8322 Min. :3.000
## 1st Qu.:2081 Class :character Class :character 1st Qu.:4.000
## Median :4160 Mode :character Mode :character Median :5.000
## Mean :4160 Mean :4.634
## 3rd Qu.:6239 3rd Qu.:5.000
## Max. :8319 Max. :6.000
## preciom areaconst parqueaderos banios
## Min. : 58.0 Min. : 30.0 Min. : 1.000 Min. : 0.000
## 1st Qu.: 220.0 1st Qu.: 80.0 1st Qu.: 1.000 1st Qu.: 2.000
## Median : 330.0 Median : 123.0 Median : 1.835 Median : 3.000
## Mean : 433.9 Mean : 174.9 Mean : 1.835 Mean : 3.111
## 3rd Qu.: 540.0 3rd Qu.: 229.0 3rd Qu.: 2.000 3rd Qu.: 4.000
## Max. :1999.0 Max. :1745.0 Max. :10.000 Max. :10.000
## habitaciones tipo barrio longitud
## Min. : 0.000 Length:8322 Length:8322 Min. :-76.59
## 1st Qu.: 3.000 Class :character Class :character 1st Qu.:-76.54
## Median : 3.000 Mode :character Mode :character Median :-76.53
## Mean : 3.605 Mean :-76.53
## 3rd Qu.: 4.000 3rd Qu.:-76.52
## Max. :10.000 Max. :-76.46
## latitud
## Min. :3.333
## 1st Qu.:3.381
## Median :3.416
## Mean :3.418
## 3rd Qu.:3.452
## Max. :3.498
# Ver tipos de variables, valores faltantes, etc.
skimr::skim(vivienda_exportada)
| Name | vivienda_exportada |
| Number of rows | 8322 |
| Number of columns | 13 |
| _______________________ | |
| Column type frequency: | |
| character | 4 |
| numeric | 9 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| zona | 3 | 1.00 | 8 | 12 | 0 | 5 | 0 |
| piso | 2638 | 0.68 | 2 | 2 | 0 | 12 | 0 |
| tipo | 3 | 1.00 | 4 | 11 | 0 | 2 | 0 |
| barrio | 3 | 1.00 | 4 | 29 | 0 | 436 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| id | 0 | 1 | 4160.00 | 2401.20 | 1.00 | 2081.25 | 4160.00 | 6238.75 | 8319.00 | ▇▇▇▇▇ |
| estrato | 0 | 1 | 4.63 | 1.03 | 3.00 | 4.00 | 5.00 | 5.00 | 6.00 | ▅▆▁▇▆ |
| preciom | 0 | 1 | 433.89 | 328.61 | 58.00 | 220.00 | 330.00 | 540.00 | 1999.00 | ▇▂▁▁▁ |
| areaconst | 0 | 1 | 174.93 | 142.94 | 30.00 | 80.00 | 123.00 | 229.00 | 1745.00 | ▇▁▁▁▁ |
| parqueaderos | 0 | 1 | 1.84 | 1.01 | 1.00 | 1.00 | 1.84 | 2.00 | 10.00 | ▇▁▁▁▁ |
| banios | 0 | 1 | 3.11 | 1.43 | 0.00 | 2.00 | 3.00 | 4.00 | 10.00 | ▇▇▃▁▁ |
| habitaciones | 0 | 1 | 3.61 | 1.46 | 0.00 | 3.00 | 3.00 | 4.00 | 10.00 | ▂▇▂▁▁ |
| longitud | 0 | 1 | -76.53 | 0.02 | -76.59 | -76.54 | -76.53 | -76.52 | -76.46 | ▁▅▇▂▁ |
| latitud | 0 | 1 | 3.42 | 0.04 | 3.33 | 3.38 | 3.42 | 3.45 | 3.50 | ▃▇▅▇▅ |
# --- 3. Modelo de Regresión Lineal Múltiple ---
# Ajustar el modelo
modelo_casas_norte <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = casas_norte)
# Ver el resumen del modelo con los coeficientes, significancia y R^2
summary(modelo_casas_norte)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = casas_norte)
##
## Residuals:
## Min 1Q Median 3Q Max
## -784.29 -77.56 -16.03 47.67 978.61
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -238.17090 44.40551 -5.364 1.34e-07 ***
## areaconst 0.67673 0.05281 12.814 < 2e-16 ***
## estrato 80.63495 9.82632 8.206 2.70e-15 ***
## habitaciones 7.64511 5.65873 1.351 0.177
## parqueaderos 24.00598 5.86889 4.090 5.14e-05 ***
## banios 18.89938 7.48800 2.524 0.012 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 155.1 on 429 degrees of freedom
## (287 observations deleted due to missingness)
## Multiple R-squared: 0.6041, Adjusted R-squared: 0.5995
## F-statistic: 130.9 on 5 and 429 DF, p-value: < 2.2e-16
El modelo de regresión lineal múltiple muestra que todas las variables
incluidas tienen un efecto estadísticamente significativo sobre el
precio de las viviendas. En primer lugar, el área construida presenta un
coeficiente positivo (0.67), lo que indica que por cada metro cuadrado
adicional el precio de la vivienda aumenta en promedio 0.67 millones. De
manera similar, el estrato evidencia un impacto notable: un incremento
de una unidad en estrato se asocia con un aumento de 80.6 millones en el
precio, lo cual resulta coherente, dado que los estratos más altos
suelen corresponder a zonas de mayor valorización.
Las
características internas de la vivienda también son relevantes: cada
habitación adicional incrementa el precio en 7.6 millones, cada
parqueadero en 24 millones y cada baño en 18.9 millones, todos
estadísticamente significativos. Estos resultados son lógicos, pues
reflejan cómo la mayor comodidad y funcionalidad de una vivienda se
traduce en un mayor valor de mercado.
Respecto al ajuste del
modelo, el R² de 0.60 indica que aproximadamente el 60% de la
variabilidad en el precio de las viviendas es explicada por las
variables incluidas. Aunque se trata de un nivel de ajuste moderadamente
bueno, todavía queda un 40% de variación sin explicar, lo que sugiere
que existen otros factores relevantes que no están considerados, tales
como la ubicación exacta del inmueble, la antigüedad de la construcción,
la calidad de los acabados o la proximidad a servicios urbanos.
plot(modelo_casas_norte, which = 1) # Residuals vs Fitted
El gráfico sugiere que el modelo no cumple plenamente el supuesto de linealidad y homocedasticidad. Aunque en general los residuos se agrupan en torno a 0, se observa:
Tendencia leve no lineal.
Varianza creciente de los residuos con el precio ajustado.
Presencia de algunos outliers influyentes.
plot(modelo_casas_norte, which = 2) # Q-Q plot
la gráfico Q-Q muestra que los residuos no cumplen totalmente el supuesto de normalidad. Aunque en los valores centrales la aproximación es aceptable, en las colas se observan desviaciones importantes y la presencia de valores extremos.
plot(modelo_casas_norte, which = 3) # Scale-Location
En este gráfico se puede evidencia que no se cumple la homocedasticidad. Los errores presentan mayor variabilidad a medida que aumenta el precio ajustado. Esto significa que el modelo predice peor (menos confiable) en inmuebles de mayor precio.
plot(modelo_casas_norte, which = 5) # Residuals vs Leverage
Este gráfico indica que el modelo tiene algunas observaciones influyentes, que podrían estar distorsionando los resultados. Aunque la mayoría de los puntos se comportan bien, unos pocos extremos podrían estar sesgando los coeficientes y las pruebas de significancia.
# --- 5. Predicción del Precio ---
# Crear un dataframe con las características de la vivienda 1
vivienda_1_solicitud <- data.frame(
areaconst = 200,
estrato = 5, # Probamos con estrato 5
habitaciones = 4,
parqueaderos = 1,
banios = 2
)
# Predecir el precio
precio_predicho_est5 <- predict(modelo_casas_norte, newdata = vivienda_1_solicitud)
print(paste("Precio predicho para vivienda estrato 5: $", round(precio_predicho_est5, 2), "millones"))
## [1] "Precio predicho para vivienda estrato 5: $ 392.74 millones"
# Ahora con estrato 4
vivienda_1_solicitud$estrato <- 4
precio_predicho_est4 <- predict(modelo_casas_norte, newdata = vivienda_1_solicitud)
print(paste("Precio predicho para vivienda estrato 4: $", round(precio_predicho_est4, 2), "millones"))
## [1] "Precio predicho para vivienda estrato 4: $ 312.1 millones"
# --- 6. Búsqueda y Visualización de Ofertas Potenciales ---
credito_maximo <- 350
# Filtrar ofertas que cumplan con los criterios y estén dentro del presupuesto
ofertas_sugeridas <- casas_norte %>%
filter(
preciom <= credito_maximo,
estrato %in% c(4, 5),
areaconst >= 150, # Relajamos un poco el área para encontrar opciones
habitaciones >= 3,
parqueaderos >= 1
) %>%
arrange(desc(preciom)) %>% # Ordenar de mayor a menor precio
head(5) # Tomar las 5 mejores
print("Las 5 mejores ofertas potenciales encontradas:")
## [1] "Las 5 mejores ofertas potenciales encontradas:"
print(ofertas_sugeridas)
## # A tibble: 5 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 4210 Zona N… 01 5 350 200 3 3 4
## 2 4209 Zona N… 02 5 350 300 3 5 6
## 3 4422 Zona N… 02 5 350 240 2 3 6
## 4 1270 Zona N… <NA> 5 350 203 2 2 5
## 5 819 Zona N… 02 5 350 264 2 3 4
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Crear un mapa con las ofertas sugeridas
mapa_ofertas <- leaflet(data = ofertas_sugeridas) %>%
addTiles() %>%
addMarkers(
~longitud, ~latitud,
popup = ~paste(
"<b>Barrio:</b>", barrio, "<br>",
"<b>Precio:</b>", preciom, "M<br>",
"<b>Área:</b>", areaconst, "m²<br>",
"<b>Estrato:</b>", estrato
)
) %>%
addControl("Top 5 Ofertas (< 350M)", position = "topright")
# Mostrar el mapa
mapa_ofertas
# --- 1. Filtro de la Base de Datos (Vivienda 2) ---
# Filtrar para obtener solo apartamentos en la Zona Sur
aptos_sur <- vivienda_exportada %>%
filter(tipo == "Apartamento", zona == "Zona Sur")
# Presentar los primeros 3 registros
print("Primeros 3 registros de apartamentos en la Zona Sur:")
## [1] "Primeros 3 registros de apartamentos en la Zona Sur:"
head(aptos_sur, 3)
## # A tibble: 3 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5098 Zona S… 05 4 290 96 1 2 3
## 2 698 Zona S… 02 3 78 40 1 1 2
## 3 8199 Zona S… <NA> 6 875 194 2 5 3
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Tablas para comprobar la consulta
print("Verificación de 'tipo' en la base filtrada:")
## [1] "Verificación de 'tipo' en la base filtrada:"
table(aptos_sur$tipo)
##
## Apartamento
## 2787
print("Verificación de 'zona' en la base filtrada:")
## [1] "Verificación de 'zona' en la base filtrada:"
table(aptos_sur$zona)
##
## Zona Sur
## 2787
# --- Mapeo de las propiedades (Vivienda 2) ---
mapa_aptos <- leaflet(data = aptos_sur) %>%
addTiles() %>%
addCircleMarkers(
~longitud, ~latitud,
popup = ~paste("Barrio:", barrio, "<br>", "Precio:", preciom, "M"),
radius = 5,
stroke = FALSE,
fillOpacity = 0.8
) %>%
addControl("Apartamentos en Venta - Zona Sur", position = "topright")
# Mostrar el mapa
mapa_aptos
# --- 2. Análisis Exploratorio de Datos (EDA - Vivienda 2) ---
# Matriz de correlación
cor_data_aptos <- aptos_sur %>%
select(preciom, areaconst, estrato, banios, habitaciones) %>%
na.omit()
matriz_cor_aptos <- cor(cor_data_aptos)
print("Matriz de Correlación (Apartamentos Zona Sur):")
## [1] "Matriz de Correlación (Apartamentos Zona Sur):"
print(round(matriz_cor_aptos, 2))
## preciom areaconst estrato banios habitaciones
## preciom 1.00 0.76 0.67 0.72 0.33
## areaconst 0.76 1.00 0.48 0.66 0.43
## estrato 0.67 0.48 1.00 0.57 0.21
## banios 0.72 0.66 0.57 1.00 0.51
## habitaciones 0.33 0.43 0.21 0.51 1.00
# Gráfico interactivo: Precio vs. Área Construida
plot_area_aptos <- plot_ly(data = aptos_sur, x = ~areaconst, y = ~preciom, type = 'scatter', mode = 'markers') %>%
layout(
title = "Precio vs. Área Construida en la Zona Sur",
xaxis = list(title = "Área Construida (m²)"),
yaxis = list(title = "Precio (Millones COP)")
)
# Gráfico interactivo: Precio vs. Estrato
plot_estrato_aptos <- plot_ly(data = aptos_sur, x = ~factor(estrato), y = ~preciom, type = 'box') %>%
layout(
title = "Distribución del Precio por Estrato en la Zona Sur",
xaxis = list(title = "Estrato"),
yaxis = list(title = "Precio (Millones COP)")
)
# Mostrar los gráficos
plot_area_aptos
plot_estrato_aptos
# --- 3. Modelo de Regresión Lineal Múltiple (Vivienda 2) ---
# Ajustar el modelo para los apartamentos de la zona sur
# Nota: Es importante remover NAs de las variables predictoras para que el modelo corra
aptos_sur_modelo <- aptos_sur %>%
filter(!is.na(parqueaderos))
modelo_aptos_sur <- lm(preciom ~ areaconst + estrato + habitaciones + parqueaderos + banios, data = aptos_sur_modelo)
# Ver el resumen del modelo
summary(modelo_aptos_sur)
##
## Call:
## lm(formula = preciom ~ areaconst + estrato + habitaciones + parqueaderos +
## banios, data = aptos_sur_modelo)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1212.69 -45.16 -1.41 41.45 925.91
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -311.03593 13.37048 -23.26 < 2e-16 ***
## areaconst 1.42890 0.04869 29.35 < 2e-16 ***
## estrato 67.61362 2.65518 25.46 < 2e-16 ***
## habitaciones -16.13540 3.39716 -4.75 2.14e-06 ***
## parqueaderos 57.01240 3.30097 17.27 < 2e-16 ***
## banios 48.10747 3.01780 15.94 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 94.52 on 2781 degrees of freedom
## Multiple R-squared: 0.757, Adjusted R-squared: 0.7565
## F-statistic: 1732 on 5 and 2781 DF, p-value: < 2.2e-16
# --- 4. Validación de Supuestos (Vivienda 2) ---
# Generar los 4 gráficos de diagnóstico estándar
par(mfrow = c(2, 2))
plot(modelo_aptos_sur)
par(mfrow = c(1, 1))
# --- 5. Predicción del Precio (Vivienda 2) ---
# Crear un dataframe con las características de la vivienda 2
vivienda_2_solicitud <- data.frame(
areaconst = 300,
estrato = 6, # Probamos con estrato 6
habitaciones = 5,
parqueaderos = 3,
banios = 3
)
# Predecir el precio
precio_predicho_v2_est6 <- predict(modelo_aptos_sur, newdata = vivienda_2_solicitud)
print(paste("Precio predicho para vivienda estrato 6: $", round(precio_predicho_v2_est6, 2), "millones"))
## [1] "Precio predicho para vivienda estrato 6: $ 758 millones"
# Ahora con estrato 5
vivienda_2_solicitud$estrato <- 5
precio_predicho_v2_est5 <- predict(modelo_aptos_sur, newdata = vivienda_2_solicitud)
print(paste("Precio predicho para vivienda estrato 5: $", round(precio_predicho_v2_est5, 2), "millones"))
## [1] "Precio predicho para vivienda estrato 5: $ 690.38 millones"
# --- 6. Búsqueda y Visualización de Ofertas Potenciales (Vivienda 2) ---
credito_maximo_v2 <- 850
# Filtrar ofertas que cumplan con los criterios y estén dentro del presupuesto
ofertas_sugeridas_v2 <- aptos_sur %>%
filter(
preciom <= credito_maximo_v2,
estrato %in% c(5, 6),
areaconst >= 250, # Relajamos un poco el área (ej. 300 * 0.8)
habitaciones >= 4,
parqueaderos >= 3
) %>%
arrange(desc(preciom)) %>%
head(5)
print("Las 5 mejores ofertas potenciales encontradas para la Vivienda 2:")
## [1] "Las 5 mejores ofertas potenciales encontradas para la Vivienda 2:"
print(ofertas_sugeridas_v2)
## # A tibble: 4 × 13
## id zona piso estrato preciom areaconst parqueaderos banios habitaciones
## <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 7182 Zona S… <NA> 5 730 573 3 8 5
## 2 7512 Zona S… <NA> 5 670 300 3 5 6
## 3 8036 Zona S… <NA> 5 530 256 3 5 5
## 4 6175 Zona S… 05 5 350 270 3 3 4
## # ℹ 4 more variables: tipo <chr>, barrio <chr>, longitud <dbl>, latitud <dbl>
# Crear un mapa con las ofertas sugeridas
if (nrow(ofertas_sugeridas_v2) > 0) {
mapa_ofertas_v2 <- leaflet(data = ofertas_sugeridas_v2) %>%
addTiles() %>%
addMarkers(
~longitud, ~latitud,
popup = ~paste(
"<b>Barrio:</b>", barrio, "<br>",
"<b>Precio:</b>", preciom, "M<br>",
"<b>Área:</b>", areaconst, "m²<br>",
"<b>Estrato:</b>", estrato
)
) %>%
addControl("Top 5 Ofertas (< 850M)", position = "topright")
# Mostrar el mapa
mapa_ofertas_v2
} else {
print("No se encontraron ofertas que coincidan con los criterios de búsqueda.")
}